package org.budo.spring.mvc.view.jphp.launcher;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.ServiceLoader;
import java.util.Set;

import org.budo.support.slf4j.Slf4j;
import org.develnext.jphp.core.compiler.jvm.JvmCompiler;
import org.develnext.jphp.core.opcode.ModuleOpcodePrinter;
import org.slf4j.Logger;
import org.springframework.core.io.Resource;

import php.runtime.Information;
import php.runtime.Memory;
import php.runtime.common.StringUtils;
import php.runtime.env.CallStackItem;
import php.runtime.env.ConcurrentEnvironment;
import php.runtime.env.Context;
import php.runtime.env.Environment;
import php.runtime.env.TraceInfo;
import php.runtime.exceptions.support.ErrorType;
import php.runtime.ext.core.classes.WrapClassLoader;
import php.runtime.ext.support.Extension;
import php.runtime.launcher.LaunchException;
import php.runtime.memory.ArrayMemory;
import php.runtime.memory.StringMemory;
import php.runtime.reflection.ClassEntity;
import php.runtime.reflection.ModuleEntity;
import php.runtime.reflection.support.ReflectionUtils;

/**
 * @author li
 * @see php.runtime.launcher.Launcher
 */
public class JphpLauncher extends AbstractJphpLauncher {
    private static final Logger log = Slf4j.getLogger();

    public void run(Resource templateResource, boolean mustBootstrap, boolean disableExtensions) {
        try {
            this.doRun(templateResource, mustBootstrap, disableExtensions);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    private void doRun(Resource templateResource, boolean mustBootstrap, boolean disableExtensions) throws Throwable {
        this.getCompileScope().configuration = new HashMap<String, Memory>();

        if (!disableExtensions) {
            this.initExtensions();
        }

        String classLoader = this.getConfig().getProperty("env.classLoader", ReflectionUtils.getClassName(WrapClassLoader.WrapLauncherClassLoader.class));

        if (classLoader != null && !(classLoader.isEmpty())) {
            ClassEntity classLoaderEntity = this.getEnvironment().fetchClass(classLoader);

            if (classLoaderEntity == null) {
                throw new LaunchException("Class loader class is not found: " + classLoader);
            }

            WrapClassLoader loader = classLoaderEntity.newObject(this.getEnvironment(), TraceInfo.UNKNOWN, true);
            this.getEnvironment().invokeMethod(loader, "register", Memory.TRUE);
        }

        ModuleEntity bootstrap = this.loadTemplateFromResource(templateResource); // 模板

        if (bootstrap == null) {
            throw new IOException("ModuleEntity bootstrap is null, templateResource=" + templateResource);
        }

        if (new StringMemory(this.getConfig().getProperty("bootstrap.showBytecode", "")).toBoolean()) {
            ModuleOpcodePrinter moduleOpcodePrinter = new ModuleOpcodePrinter(bootstrap);
            log.info("moduleOpcodePrinter=" + moduleOpcodePrinter.toString());
        }

        this.initModule(bootstrap);

        ArrayMemory argv = ArrayMemory.ofStrings(new String[0]);
        argv.unshift(Memory.NULL);

        this.getEnvironment().getGlobals().put("argv", argv);
        this.getEnvironment().pushCall(new CallStackItem(new TraceInfo(bootstrap.getName(), -1, -1)));

        try {
            log.debug("this=" + this + ", bootstrap=" + bootstrap + ", environment=" + this.getEnvironment());

            bootstrap.includeNoThrow(this.getEnvironment()); // 执行
        } finally {
            this.getEnvironment().popCall();
            this.getCompileScope().triggerProgramShutdown(this.getEnvironment());

            if (StringMemory.valueOf(this.getConfig().getProperty("env.doFinal", "1")).toBoolean()) {
                this.getEnvironment().doFinal();
            }
        }
    }

    private void initModule(ModuleEntity moduleEntity) {
        this.getCompileScope().loadModule(moduleEntity);
        this.getCompileScope().addUserModule(moduleEntity);
        this.getEnvironment().registerModule(moduleEntity);
    }

    private void initExtensions() {
        String tmp = this.getConfigValue("env.extensions", "spl").toString();
        String[] _extensions = StringUtils.split(tmp, ",");

        Set<String> extensions = new HashSet<String>();
        for (String ext : _extensions) {
            extensions.add(ext.trim());
        }

        ServiceLoader<Extension> loader = ServiceLoader.load(Extension.class);

        for (Extension extension : loader) {
            this.getCompileScope().registerExtension(extension);
        }

        for (String ext : extensions) {
            String className = Information.EXTENSIONS.get(ext.trim().toLowerCase());
            if (className == null) {
                className = ext.trim();
            }

            this.getCompileScope().registerExtension(className);
        }

        Environment environment = this.getConfigValue("env.concurrent", "1").toBoolean() //
                ? new ConcurrentEnvironment(this.getCompileScope(), this.getOutputStream()) //
                : new Environment(this.getCompileScope(), this.getOutputStream());

        this.setEnvironment(environment);

        this.getEnvironment().setErrorFlags(ErrorType.E_ALL.value ^ ErrorType.E_NOTICE.value);
        this.getEnvironment().getDefaultBuffer().setImplicitFlush(true);
    }

    private ModuleEntity loadTemplateFromResource(Resource templateResource) throws IOException {
        InputStream inputStream = templateResource.getInputStream();
        String fileName = templateResource.getFilename() + "_" + templateResource.hashCode();
        Charset character = this.getEnvironment().getDefaultCharset();

        Context context = new Context(inputStream, fileName, character);

        JvmCompiler compiler = new JvmCompiler(this.getEnvironment(), context);
        ModuleEntity moduleEntity = compiler.compile(false);

        // File file = new File("1.class");
        // System.err.println(FileUtil.getCanonicalPath(file));
        // IoUtil.write(new ByteArrayInputStream(moduleEntity.getData()), new FileOutputStream(file));

        log.debug("loadTemplateFromResource, templateResource=" + templateResource + ", inputStream=" + inputStream //
                + ", fileName=" + fileName + ", character=" + character + ", context=" + context + ", compiler=" + compiler + ", moduleEntity=" + moduleEntity);

        return moduleEntity;
    }
}